module ByValue(eval) where

import Dict
import Lenguaje
import Maybe -- Para trabajar más facilmente con el tipo da datos Maybe.

-- Un entorno es un mapeo de variables a valores.
type Environment = Dict VarId Value 

-- *** Ejercicios 3 y 4 ***

-- Evalúa una expresión y devuelve el valor.
eval :: ProgramDef -> Exp -> Value
eval pr e = eval' pr e emptyDict 

-- Función auxiliar provista por la cátedra a modo de ayuda que trabaja con entornos no fijos 
-- para lidiar con el problema de que en el let venga un valor sin la asignación primero
-- es decir en la función foldExp todas las funciones devuelven una función que toma un entorno y devuelve
-- un valor para que de éste modo sólo se pueda devolver un valor cuando tenga el entorno correcto.
eval' :: ProgramDef ->  Exp -> Environment -> Value
eval' pr e = (foldExp  
       (\v -> (\en -> v))
	   (\var -> (\en -> fromJust(lookupDict en var)))
	   (\op v1 v2 -> (\en -> tradOp op (v1 en) (v2 en)))
	   (\v1 v2 v3 -> (\en -> if (v1 en == 0) then v2 en else v3 en))
       (\var v1 v2 ->(\en ->  seq (v1 en) v2(extendDict en var (v1 en))))
       (\func xs -> (\en -> (reducCall (fromJust(lookupDict pr func)) (map (p en) xs) pr en)))
       (e))

-- Función auxiliar que básicamente interpreta el call llamando a la función correspondiente
-- y asociando las variables a los valores
-- el seq (product vlues) resto me permite evaluar call by value ya que primero busca la productoria
-- entre los parámetros y para ello debe calcularlos antes de continuar con el resto.

reducCall :: FuncDef  -> [Value] -> ProgramDef -> Environment -> Value
reducCall (FuncDef vs e) vlues pr en = seq (product vlues)(eval' pr e (extendMDict en (zipWith (\a b -> (a,b)) vs vlues)))

-- Función auxiliar que traduce como opera un operador(para que eval' sea más legible). 
tradOp :: Op -> Value -> Value -> Value
tradOp Add v1 v2 = v1 + v2
tradOp Sub v1 v2 = v1 - v2
tradOp Mul v1 v2 = v1 * v2

-- Otra función auxiliar que me sirve para el map utilizado en eval'.
p :: a ->(a->b)->b
p x f = f x

